package com.alibaba.dubbo.governance.web.governance.module.screen;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.fileupload.FileItem;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.citrus.service.requestcontext.parser.ParameterParser;
import com.alibaba.citrus.service.requestcontext.parser.ParserRequestContext;
import com.alibaba.citrus.service.requestcontext.util.RequestContextUtil;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.governance.service.ProviderService;
import com.alibaba.dubbo.governance.service.RestService;
import com.alibaba.dubbo.governance.service.ZookeeperService;
import com.alibaba.dubbo.governance.web.common.module.screen.Restful;
import com.alibaba.dubbo.registry.common.domain.Invocation;
import com.alibaba.fastjson.JSON;

public class Rest extends Restful {

    @Autowired
    private ProviderService providerService;
    @Autowired
    private RestService restService;
    @Autowired
    private ZookeeperService zookeeperService;

    @Autowired
    private HttpServletRequest request;

    /**
     * Rest index page
     */
    public void index(Map<String, Object> context) {
        Object isPassedBy = "false";
        if (context.get("argName") != null && context.get("method") != null) {
            StringBuffer path = new StringBuffer();
            path.append(context.get("service")).append("/")
                .append(context.get("method")).append("/")
                .append(context.get("argName"));

            String argumentStr = restService.getArgByName(path.toString());

            if (StringUtils.isNotEmpty(argumentStr)) {
                try {
                    JSONObject params = JSONObject.fromObject(argumentStr);
                    isPassedBy = params.get("isPassedBy");
                    JSONArray arguments = params.getJSONArray("arguments");

                    for (int i = 0; i < arguments.size(); i++) {
                        JSONObject obj = arguments.getJSONObject(i);
                        obj.put("index", i);
                    }

                    if (arguments.size() > 0) context.put("arguments", arguments);
                } catch (Exception e) {
                }
            }
        }

        initRest(context);

        context.put("isPassedBy", isPassedBy == null ? "false" : isPassedBy);
    }

    private void initRest(Map<String, Object> context) {
        List<String> methodList = new ArrayList<String>();
        String service = (String) context.get("service");

        if (StringUtils.isNotEmpty(service)) {
            methodList.addAll(providerService.findMethodsByService(service));
        }

        context.put("methods", methodList);
        context.put("zkAddress", zookeeperService.getZkAddress());
    }

    /**
     * Dubbo rest call method
     * @param invocation
     * @param context
     */
    public void call(Invocation invocation, Map<String, Object> context) {
        context.put("hideLayout", true);

        JSONObject obj = new JSONObject();

        if (! catchParams(invocation, context)) {
            obj.put("msg", "请求失败");
            context.put("result", obj.toString());
            return;
        }

        // 解决服务路径问题 apiTest/com.lvmama.dubbo.TestService
        String service = invocation.getService();
        String[] strs = service.split("/");
        if (strs.length > 2) {
            obj.put("msg", "接口路径有问题");
            obj.put("service", service);
            context.put("result", obj.toString());
            return;
        } else if (strs.length == 2 && strs[1].startsWith("com.")) {
            invocation.setService(strs[1]);
        }

        Object result = restService.call(invocation);
        context.put("result", String.valueOf(result));

    }

    private static final String ARG_TYPES = "argTypes";
    private static final String ARGUMENTS = "arguments";
    /**
     * Parameters are stored in context, used index like arg0,arg1 to distinguish
     * @param invocation
     * @param context
     * @return
     */
    private boolean catchParams(Invocation invocation, Map<String, Object> context) {
        boolean flag = false;

        List<String> argTypes = new ArrayList<String>();
        List<String> arguments = new ArrayList<String>();

        Set<Integer> indexes = new TreeSet<Integer>();

        for (Map.Entry<String, Object> param : context.entrySet()) {
            String key = param.getKey().trim();
            if (! (param.getValue() instanceof String)) continue;

            String argTypesValue = (String) param.getValue();

            if (key.startsWith(ARG_TYPES) && argTypesValue != null && argTypesValue.trim().length() > 0) {
                String index = key.substring(ARG_TYPES.length());
                String argumentValue = (String) context.get(ARGUMENTS + index);

                indexes.add(Integer.valueOf(index));

//                if (argumentValue != null && argumentValue.trim().length() > 0) {
                    argTypes.add(argTypesValue);
                    arguments.add(argumentValue);
//                }
            }
        }

        if ((argTypes.size() > 0 && arguments.size() == argTypes.size())
                || (argTypes.size() == 0 && arguments.size() == 0)) {
            flag = true;
            String[] tempArgTypes = new String[argTypes.size()];
            Object[] tempArguments = new Object[argTypes.size()];

            Iterator<Integer> ite = indexes.iterator();
            for (int i = 0; ite.hasNext(); i++) {
                Integer index = ite.next();
                tempArgTypes[i] = (String) context.get(ARG_TYPES + index);
                tempArguments[i] = (String) context.get(ARGUMENTS + index);
            }

            invocation.setArgTypes(tempArgTypes);
            invocation.setArguments(tempArguments);
        }

        return flag;
    }

    /**
     * Upload parameters method, return to index page not redirect.
     * Modified execute method in super class doing nothing when returned string is "rest.upload"
     * @param context
     * @throws DocumentException 
     */
    public String add(Map<String, Object> context) throws Exception {

        ParserRequestContext parserRequestContext =
                RequestContextUtil.findRequestContext(request, ParserRequestContext.class);

        ParameterParser params = parserRequestContext.getParameters();
        FileItem myfile = params.getFileItem("paramFile");
        InputStream inputStream = myfile.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        StringBuffer sb = new StringBuffer();
        String line = null;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }

        parseXML(sb.toString(), context);
        initRest(context);

        context.put("rundata.layout", "index");
        context.put("rundata.target", context.get("rundata.target") + "/" + "index");

        return "rest.add";
    }

    @SuppressWarnings("unchecked")
    private static void parseXML(String xml, Map<String, Object> context) throws DocumentException {
        JSONArray arguments = new JSONArray();

        Document document = DocumentHelper.parseText(xml);
        Element root = document.getRootElement();
        for (Iterator<?> i = root.elementIterator(); i.hasNext();) {
            Element e = (Element) i.next();
            if ("args".equals(e.getName())) {
                List<Element> args = e.elements("arg");
                for (int j = 0; j < args.size(); j++) {
                    Element arg = args.get(j);
                    JSONObject obj = new JSONObject();
                    for (Iterator<?> a = arg.elementIterator(); a.hasNext();) {
                        Element arge = (Element) a.next();
                        obj.put(arge.getName(), arge.getData());
                        obj.put("index", j);
                    }
                    arguments.add(obj);
                }
            } else {
                context.put(e.getName(), e.getData());
            }
        }

        if (arguments.size() > 0) context.put("arguments", arguments);
    }

    /**
     * Save parameters to zookeeper
     * The path is /dubboRest/{service}/{method}/{argName}/{parameters}
     */
    public void zkParams(Map<String, Object> context) {
        String result = "保存失败";
        JSONObject arguments = parseZkParams(context);

        if (doCheck(context) && arguments.getJSONArray("arguments").size() > 0) {
            StringBuffer path = new StringBuffer();
            path.append(context.get("service")).append("/")
                .append(context.get("method")).append("/")
                .append(context.get("argName")).append("/")
                .append(JSON.toJSONString(arguments));

            restService.saveParamToZK(path.toString());

            result = "保存成功";
        }

        context.put("hideLayout", true);
        context.put("result", result);
    }

    private boolean doCheck(Map<String, Object> context) {
        if (context.get("argName") != null
                && context.get("service") != null
                && context.get("method") != null) {

            return true;
        }
        return false;
    }

    private JSONObject parseZkParams(Map<String, Object> context) {
        JSONObject obj = new JSONObject();

        JSONArray arguments = new JSONArray();
        for (Map.Entry<String, Object> param : context.entrySet()) {
            String key = param.getKey().trim();
            if (! (param.getValue() instanceof String)) continue;

            String argTypesValue = (String) param.getValue();

            if (key.startsWith(ARG_TYPES) && argTypesValue != null && argTypesValue.trim().length() > 0) {
                String index = key.substring(ARG_TYPES.length());
                String argumentValue = (String) context.get(ARGUMENTS + index);

                if (argumentValue != null && argumentValue.trim().length() > 0) {
                    JSONObject argument = new JSONObject();
                    argument.put("argType", argTypesValue);
                    argument.put("argument", argumentValue);
                    arguments.add(argument);
                }
            }
        }

        obj.put("arguments", arguments);
        obj.put("isPassedBy", context.get("isPassedBy"));
        return obj;
    }

    /**
     * Load all saved argNames by service/method
     */
    public void loadArgs(Map<String, Object> context) {
        context.put("hideLayout", true);

        StringBuffer path = new StringBuffer();
        path.append(context.get("service")).append("/")
            .append(context.get("method"));

        List<String> argNames = restService.getArgNames(path.toString());
        StringBuffer sb = new StringBuffer();
        for (String str : argNames) {
            sb.append(str).append("###");
        }

        if (sb.length() > 3) sb.delete(sb.lastIndexOf("###"), sb.length());

        context.put("result", sb.toString());
    }

    /**
     * Delete parameters by name
     */
    public boolean deleteArgName(Map<String, Object> context) {
        if (doCheck(context)) {
            StringBuffer path = new StringBuffer();
            path.append(context.get("service")).append("/")
                .append(context.get("method")).append("/")
                .append(context.get("argName")).append("/");

            zookeeperService.deleteRecursive(path.toString());

            return true;
        }
        return false;
    }

//  public static void main(String[] args) throws DocumentException {
//      String xml = "<?xml version='1.0' encoding='UTF-8'?>" +
//          "<invocation>" +
//              "<zkAddress>127.0.0.1:2181</zkAddress>" +
//              "<service>com.lvmama.dubbo.service.DemoService</service>" +
//              "<isPassedBy>false</isPassedBy>" +
//              "<method>test</method>" +
//              "<args>" +
//                  "<arg>" +
//                      "<argType>java.lang.String</argType>" +
//                      "<argument>grylls</argument>" +
//                  "</arg>" +
//              "</args>" +
//          "</invocation>";
//      parseXML(xml, new HashMap<String, Object>());
//  }
}
